package ThreadingSafety;

/**
 *  自己实现阻塞队列. (循环队列实现) --> (一些解释看板书, 因此先看板书, 再看IDEA.)
 */

class MyBlackingQueue {
    private final int Default_Capacity = 100; // 阻塞队列的默认容量
    public int[] items = new int[Default_Capacity]; // 基于数组实现
    public volatile int head = 0; // 永远指向队首
    public volatile int tail = 0; // 永远指向队尾
    public volatile int size = 0; // 有效数据个数

    // 入队
    public void put(int elem) throws InterruptedException {
        // 修改操作 要加锁
        synchronized (this) {
            // 判满
            while(size >= items.length) {
                // 队列为满就 阻塞等待, 由 出队成功 操作唤醒
                //return;
                this.wait(); // 啥时候被唤醒不得而知,唤醒之后队列一定为不为满了嘛?? 不一定!!!
                             // 因为wait可能被 出队成功 唤醒, 也可能被提前唤醒,比如interrupt,
                             // 所以被唤醒后要在此判断队列是否为满~~直到队列非满才继续往下执行~~ 因此此处用 while!! 下同.
            }
            this.items[tail] = elem;
            tail++;
            // 当 tail == items.length时, 令tail变为0, 这是循环队列的特性!
            tail = tail % items.length;
            size++;
            this.notify(); //唤醒队列为空时的阻塞等待
        }

    }

    // 出队, 出对头元素 阻塞队列也是先进先出
    public Integer take() throws InterruptedException {
        synchronized (this) {
            // 判空
            while(size == 0) {
                // 为空 不能出队,要阻塞, 由 入队成功 操作唤醒
                //return null;
                this.wait(); // 啥时候被唤醒不得而知,唤醒之后队列一定为不为空了嘛?? 不一定!!!
                             // 因为wait可能被 入队成功 唤醒, 也可能被提前唤醒,比如interrupt,
                             // 所以被唤醒后要在此判断队列是否为空~~直到队列非空才继续往下执行~~ 因此此处用 while!!
            }
            Integer top = items[head];
            head++;

            // 当head == items.length时, 令head变为0, 这是循环队列的特性!
            head = head % items.length;
            size--;
            this.notify(); //唤醒队列为满时的阻塞等待

            return top;
        }
    }

}

public class TestDemo21 {
    public static void main(String[] args) {
        MyBlackingQueue myBlackingQueue = new MyBlackingQueue();
        Thread produce = new Thread(() -> {
            int n = 1;
            while(true) {
                try {
                    myBlackingQueue.put(n);
                    System.out.println("生产元素 " + n);
                    n++;
                    //Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        Thread customer = new Thread(() -> {
            while(true) {
                try {
                    int n = myBlackingQueue.take();
                    System.out.println("消费元素 " + n);
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        produce.start();
        customer.start();
    }
}

// 下一个案例: 定时器 将在TestDemo22 TestDemo23里讲解.
